package bank;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.security.SecureRandom;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

@SuppressWarnings("serial")
public class Konto extends UnicastRemoteObject implements IKonto, ITransfer {

  private final int TRY_LOCK_TIME = 20;

  private final String id;// Konto ID
  private double balance; // Kontostand
  private final Semaphore lock;
  public final static String Namensdienst = "//127.0.0.1/";
  private final SecureRandom random = new SecureRandom();

 
  
  public String getId() {
    return id;
  }

  public double getBalance() {
    return balance;
  }

  public Konto(String id) throws RemoteException {
    super();

    this.id = id;
    balance = 30000.0;
    lock = new Semaphore(1);
  }

  @Override
  public double einzahlen(double betrag) throws RemoteException {

	boolean l = false;
    try {

      l = lock.tryAcquire(TRY_LOCK_TIME, TimeUnit.MILLISECONDS);

      if (!l) {
        throw new LockException("possible deadlock 333 ???");
      }

      balance += betrag;
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
    	if(l) {
    		lock.release();
    	}
    }

    return balance;
  }

  @Override
  public double abheben(double betrag) throws RemoteException {

    if (Double.compare(balance, betrag) < 0) {
      throw new KontoOperationException("Balance is too low!");
    }

    try {
      lock.acquire();
      balance -= betrag;
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      lock.release();
    }
    return balance;
  }

  @Override
  public double kontoStand() throws RemoteException {
    return balance;
  }

  @Override
  public double ueberweisen(double betrag, String kontoID) throws RemoteException {

	boolean l = false;
    try {
      if (Logger.isDebug()) {
        Logger.log("ueberweisen " + betrag + " von Konto: " + id + " nach " + kontoID);
      }
      
      l = lock.tryAcquire(TRY_LOCK_TIME, TimeUnit.MILLISECONDS);

      if (!l) {
        throw new LockException("possible deadlock 111 ????");
      }
      if (this.id.equals(kontoID)) {
        throw new KontoOperationException("Cant transfer to own Acount!");
      }
      if (Double.compare(balance, betrag) < 0) {
        throw new KontoOperationException("Balance is too low!");
      }

     boolean tranferSuccess = gutschreiben(betrag, kontoID);
    
     if (!tranferSuccess) {
    	
    	 try {
    		 Thread.sleep(random.nextInt(10));
    	 } catch (InterruptedException e) {
			// TODO: handle exception
		}
    	 
        throw new LockException("possible deadlock 222 ????");
     }
   

      balance -= betrag;

    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      if(l) {
    	  lock.release();
      }
    }
    return balance;

  }

  
  private Throwable getCause(Throwable e) {

	    while (e.getCause() != null) {
	      e = e.getCause();
	    }

	    return e;
	  }

  
  @Override
  public boolean gutschreiben(double betrag, String empfaengerKontoID) throws RemoteException {

    IKonto konto = null;
    try {

      if (Logger.isDebug()) {
        Logger.log("Gutschrift " + betrag + " an " + empfaengerKontoID);
      }

      konto = (IKonto) Naming.lookup(Namensdienst + empfaengerKontoID);
    } catch (MalformedURLException e) {
      e.printStackTrace();
      throw new KontoOperationException("Fehler bei Gutschrift", e);
    } catch (NotBoundException e) {
      e.printStackTrace();
      throw new KontoOperationException("Kein Konto gefunden: " + empfaengerKontoID);
    }

    if (konto != null) {
      try {
        konto.einzahlen(betrag);
      } catch (RemoteException e) {
        if(getCause(e) instanceof LockException) {
        	return false;        	
        } else {
        	throw e;
        }
      }
      return true;
    }

    throw new Error();
  }
}
